package org.javarosa.mesh4j.sync.adapter;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Vector;

import javax.microedition.rms.RecordEnumeration;

import org.javarosa.core.JavaRosaServiceProvider;
import org.javarosa.core.model.FormDef;
import org.javarosa.core.model.IFormDataModel;
import org.javarosa.core.model.instance.DataModelTree;
import org.javarosa.core.model.storage.DataModelTreeMetaData;
import org.javarosa.core.model.storage.DataModelTreeRMSUtility;
import org.javarosa.core.model.storage.FormDefRMSUtility;
import org.javarosa.core.services.storage.utilities.IRecordStoreEnumeration;
import org.javarosa.model.xform.XFormSerializingVisitor;
import org.javarosa.xform.parse.XFormParser;
import org.mesh4j.sync.adapters.rms.storage.RmsRecordContent;
import org.mesh4j.sync.adapters.rms.storage.RmsStorageSyncIDRmsRecordRelation;
import org.mesh4j.sync.adapters.split.IContentAdapter;
import org.mesh4j.sync.adapters.split.ISyncEntityRelationListener;
import org.mesh4j.sync.model.IContent;
import org.mesh4j.sync.validations.Guard;
import org.mesh4j.sync.validations.MeshException;

public class DataModelTreeContentAdapter implements IContentAdapter, ISyncEntityRelationListener {

	private final static String XML_HEADER = "<?xml version='1.0' ?>";
	private final static String DMT_SYNC_ID_REL_RMS_NAME = "M4X_DATA_MODEL_TREE_SYNCID_REL";
	
	// MODEL VARIABLES
	private XFormSerializingVisitor xFormSerializer;
	private RmsStorageSyncIDRmsRecordRelation rmsStorageSyncIDRmsRecordRelation;
	private String formName;
	private int formIdReference;
	
	// BUSINESS METHODS
	public DataModelTreeContentAdapter(String formName) {
		Guard.argumentNotNullOrEmptyString(formName, "formName");
		
		this.xFormSerializer = new XFormSerializingVisitor();
		this.rmsStorageSyncIDRmsRecordRelation = new RmsStorageSyncIDRmsRecordRelation(DMT_SYNC_ID_REL_RMS_NAME);
		this.formName = formName;
		
		this.formIdReference = this.getFormDefRMSUtility().getIDfromName(formName);
	}
	
	public DataModelTreeContentAdapter() {
		this.xFormSerializer = new XFormSerializingVisitor();
		this.rmsStorageSyncIDRmsRecordRelation = new RmsStorageSyncIDRmsRecordRelation(DMT_SYNC_ID_REL_RMS_NAME);
		this.formName = "";		
		this.formIdReference = -1;
	}

	public void delete(IContent entity) {
		try{
			int instanceID = this.getAssociationRMS().getInstanceID(entity.getId());
			DataModelTreeRMSUtility modelUtil = this.getDataModelTreeRMSUtility();
			modelUtil.deleteRecord(instanceID);
			this.getAssociationRMS().deleteAssociation(entity.getId());
		} catch (Exception e) {
			e.printStackTrace();
			throw new MeshException(e.getMessage());
		}
	}

	public IContent get(String syncId) {
		int instanceID = this.getAssociationRMS().getInstanceID(syncId);
		if(instanceID == -1){
			return null;
		}

		try{
			IFormDataModel theModel = new DataModelTree();
			try{
				DataModelTreeRMSUtility modelUtil = this.getDataModelTreeRMSUtility();			
				modelUtil.retrieveFromRMS(instanceID, theModel);
			}catch (Exception e) {
				e.printStackTrace();
				this.getAssociationRMS().deleteAssociation(syncId);
				return null;
			}
	
			FormDef formDef = new FormDef();
			this.getFormDefRMSUtility().retrieveFromRMS(this.formIdReference, formDef);
			String xml = serializeModel(theModel, formDef);
			return new RmsRecordContent(xml, syncId, instanceID, theModel.getName());
		} catch (Exception e) {
			e.printStackTrace();
			throw new MeshException(e.getMessage());
		}
	}

	public Vector<IContent> getAll(Date since) {
		Vector<IContent> result = new Vector<IContent>();
		DataModelTreeRMSUtility modelUtil = this.getDataModelTreeRMSUtility();
		IRecordStoreEnumeration records = null;
		try{
			FormDef formDef = new FormDef();
			this.getFormDefRMSUtility().retrieveFromRMS(this.formIdReference, formDef);
			
			records = modelUtil.enumerateMetaData();
			int instanceId = -1;
			IFormDataModel theModel = null;
			DataModelTreeMetaData metaData = null;
			while (records.hasNextElement()) {
				metaData = new DataModelTreeMetaData();
				modelUtil.retrieveMetaDataFromRMS(records.nextRecordId(), metaData);
			
				if(metaData.getFormIdReference() == this.formIdReference){

					theModel = new DataModelTree();
					instanceId = metaData.getRecordId();
					modelUtil.retrieveFromRMS(instanceId, theModel);

					if(since == null || (since != null && theModel.getDateSaved().getTime() >= since.getTime())){
						String xml = serializeModel(theModel, formDef);
						String syncID = this.getAssociationRMS().getSyncID(instanceId);		// Can be null
						result.addElement(new RmsRecordContent(xml, syncID, instanceId, theModel.getName()));
					}
				} 
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new MeshException(e.getMessage());
		}finally{
			if(records != null){
				records.destroy();
			}
		}
		return result;
	}

	public String getType() {
		return this.formName;
	}

	public IContent normalize(IContent content) {
		try{
			int recordId = this.getAssociationRMS().getInstanceID(content.getId());
			return new RmsRecordContent(content.getPayload(), content.getId(), recordId, this.getType());
		} catch (Exception e) {
			e.printStackTrace();
			throw new MeshException(e.getMessage());
		}
	}

	public void save(IContent entity) {

		try {
			int instanceID = this.getAssociationRMS().getInstanceID(entity.getId());

			DataModelTreeRMSUtility utility = this.getDataModelTreeRMSUtility();
			DataModelTree dataModelTree = this.deserializeModel(entity.getPayload());
			dataModelTree.setName(this.formName);
			dataModelTree.setFormReferenceId(this.formIdReference);
			dataModelTree.setDateSaved(new Date());
			
			if (instanceID == -1) {
				instanceID = utility.writeToRMS(dataModelTree);
				this.getAssociationRMS().addAssociation(entity.getId(), instanceID);
			} else {
				utility.updateToRMS(instanceID, dataModelTree);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new MeshException(e.getMessage());
		}
	}

	private String serializeModel(IFormDataModel theModel, FormDef formDef) throws IOException, UnsupportedEncodingException {
		if (formDef.getLocalizer() != null && formDef.getLocalizer().getLocale() == null) {
			formDef.getLocalizer().setToDefault();
		}
		
		byte[] payloadBytes = this.xFormSerializer.serializeDataModel(theModel);
		String xml = new String(payloadBytes, "UTF-8");
		xml = xml.substring(XML_HEADER.length(), xml.length());
		return xml;
	}
	
	private DataModelTree deserializeModel(String payload) throws Exception{
		FormDef formDef = new FormDef();
		this.getFormDefRMSUtility().retrieveFromRMS(this.formIdReference, formDef);
		if (formDef.getLocalizer() != null && formDef.getLocalizer().getLocale() == null) {
			formDef.getLocalizer().setToDefault();
		}
		
		
		DataModelTree dataModelTree = XFormParser.parseDataModelTree(XML_HEADER+payload, formDef);
		return dataModelTree;
	}
	
	public void notifyNewSyncForContent(String syncId, IContent content) {
		RmsRecordContent dataModelTreeContent = (RmsRecordContent) content;
		dataModelTreeContent.setSyncId(syncId);
		
		if(dataModelTreeContent.getRecordId() != -1){
			try{
				this.getAssociationRMS().addAssociation(syncId, dataModelTreeContent.getRecordId());
			} catch (Exception e) {
				e.printStackTrace();
				throw new MeshException(e.getMessage());
			}
		}		
	}

	private DataModelTreeRMSUtility getDataModelTreeRMSUtility() {
		return (DataModelTreeRMSUtility) JavaRosaServiceProvider.instance()
				.getStorageManager().getRMSStorageProvider().getUtility(
						DataModelTreeRMSUtility.getUtilityName());
	}
	
	private FormDefRMSUtility getFormDefRMSUtility() {
		return (FormDefRMSUtility) JavaRosaServiceProvider.instance()
				.getStorageManager().getRMSStorageProvider().getUtility(
						FormDefRMSUtility.getUtilityName());
	}

	private RmsStorageSyncIDRmsRecordRelation getAssociationRMS() {
		return rmsStorageSyncIDRmsRecordRelation;
	}

	public void notifyRemoveSync(String syncId) {
		try{
			this.getAssociationRMS().deleteAssociation(syncId);
		} catch (Exception e) {
			e.printStackTrace();
			throw new MeshException(e.getMessage());
		}
	}

	public void deleteRecordStorage(boolean deleteXForms) {
		this.getAssociationRMS().deleteRecordStorage();
		if(deleteXForms){
			this.getDataModelTreeRMSUtility().tempEmpty();
		}
	}

	public void deleteAll() {
		DataModelTreeRMSUtility modelUtil = this.getDataModelTreeRMSUtility();
		IRecordStoreEnumeration records = null;
		try{
			records = modelUtil.enumerateMetaData();
			int instanceId = -1;
			DataModelTreeMetaData metaData = null;
			while (records.hasNextElement()) {
				metaData = new DataModelTreeMetaData();
				modelUtil.retrieveMetaDataFromRMS(records.nextRecordId(), metaData);
			
				if(metaData.getFormIdReference() == this.formIdReference){
					
					instanceId = metaData.getRecordId();
					modelUtil.deleteRecord(instanceId);
					String syncID = this.getAssociationRMS().getSyncID(instanceId);		// Can be null
					this.getAssociationRMS().deleteAssociation(syncID);
				} 
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new MeshException(e.getMessage());
		}finally{
			if(records != null){
				records.destroy();
			}
		}
	}

}
